cmake_minimum_required(VERSION 3.10.2)
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_LIST_DIR}/CMakeModules)

###########################################################
#### Project configuration                             ####
###########################################################

project(MMMCore)

set(PROJ_VERSION 0.0.12)
set(PROJ_SO_VERSION 1) # shared lib (.so file) build number

###########################################################
#### Source configuration                              ####
###########################################################

set(SOURCE_FILES
    MMM/MathTools.cpp
    MMM/XMLTools.cpp
    MMM/Model/Model.cpp
    MMM/Motion/Motion.cpp
    MMM/Motion/Sensor.cpp
    MMM/Motion/SensorMeasurement.cpp
    MMM/Motion/SensorMeasurementType.cpp
    MMM/Motion/MotionReaderXML.cpp
    MMM/Motion/MotionWriterXML.cpp
    MMM/Motion/MotionReaderC3D.cpp
    MMM/Motion/MarkerData.cpp
    MMM/Motion/Legacy/AbstractMotion.cpp
    MMM/Motion/Legacy/LegacyMotion.cpp
    MMM/Motion/Legacy/MotionFrame.cpp
    MMM/Motion/Legacy/MarkerMotion.cpp
    MMM/Motion/Legacy/UnlabeledMarkerMotion.cpp
    MMM/Motion/Legacy/LegacyMotionReaderXML.cpp
    MMM/Motion/Legacy/LegacyMotionReaderC3D.cpp
    MMM/Motion/Legacy/Converter/MarkerBasedConverter.cpp
    MMM/Motion/Legacy/Converter/Converter.cpp
    MMM/Motion/Legacy/Converter/ConverterFactory.cpp
    MMM/Model/ModelReaderXML.cpp
    MMM/Model/ModelProcessor.cpp
    MMM/Model/ModelProcessorWinter.cpp
    MMM/Model/ModelProcessorFactory.cpp
    MMM/Model/ModelProcessorFactoryWinter.cpp
)

set(HEADER_FILES_base
    MMM/AbstractFactoryMethod.h
    MMM/FactoryPluginLoader.h
    MMM/MultipleFactoryPluginLoader.h
    MMM/Tools.h
    MMM/MathTools.h
    MMM/XMLTools.h
    MMM/Exceptions.h
    MMM/MMMCore.h
    MMM/MMMImportExport.h
)

set(HEADER_FILES_xml
    MMM/RapidXML/rapidxml.hpp
    MMM/RapidXML/rapidxml_print.hpp
    MMM/RapidXML/RapidXMLReader.h
    MMM/RapidXML/RapidXMLWriter.h
)

set(HEADER_FILES_model
    MMM/Model/ModelNode.h
    MMM/Model/Model.h
    MMM/Model/ModelProcessor.h
    MMM/Model/ModelProcessorWinter.h
    MMM/Model/ModelProcessorFactory.h
    MMM/Model/ModelProcessorFactoryWinter.h
    MMM/Model/ModelReaderXML.h
)

set(HEADER_FILES_motion
    MMM/Motion/Motion.h
    MMM/Motion/Sensor.h
    MMM/Motion/AttachedSensor.h
    MMM/Motion/BasicSensor.h
    MMM/Motion/InterpolatableSensor.h
    MMM/Motion/SensorFactory.h
    MMM/Motion/InterpolatableSensorMeasurement.h
    MMM/Motion/SensorMeasurement.h
    MMM/Motion/SensorMeasurementType.h
    MMM/Motion/MotionReaderXML.h
    MMM/Motion/MotionWriterXML.h
    MMM/Motion/MotionReaderC3D.h
    MMM/Motion/MarkerData.h
)

set(HEADER_FILES_legacymotion
    MMM/Motion/Legacy/AbstractMotion.h
    MMM/Motion/Legacy/LegacyMotion.h
    MMM/Motion/Legacy/MotionFrame.h
    MMM/Motion/Legacy/MotionEntries.h
    MMM/Motion/Legacy/MotionFrameEntries.h
    MMM/Motion/Legacy/LegacyMarkerData.h
    MMM/Motion/Legacy/UnlabeledMarkerData.h
    MMM/Motion/Legacy/MarkerMotion.h
    MMM/Motion/Legacy/UnlabeledMarkerMotion.h
    MMM/Motion/Legacy/LegacyMotionReaderXML.h
    MMM/Motion/Legacy/LegacyMotionReaderC3D.h
)

set(HEADER_FILES_legacymotionconverter
    MMM/Motion/Legacy/Converter/MarkerBasedConverter.h
    MMM/Motion/Legacy/Converter/Converter.h
    MMM/Motion/Legacy/Converter/ConverterFactory.h
)

set(HEADER_FILES_3rdParty_boost_extension
    MMM/3rdParty/boost/extension/common.hpp
    MMM/3rdParty/boost/extension/type_map.hpp
    MMM/3rdParty/boost/extension/factory.hpp
    MMM/3rdParty/boost/extension/shared_library.hpp
    MMM/3rdParty/boost/extension/extension.hpp
    MMM/3rdParty/boost/extension/adaptable_factory.hpp
    MMM/3rdParty/boost/extension/factory_map.hpp
    MMM/3rdParty/boost/extension/registry.hpp
    MMM/3rdParty/boost/extension/convenience.hpp
    MMM/3rdParty/boost/extension/filesystem.hpp
)

set(HEADER_FILES_3rdParty_boost_extension_impl
    MMM/3rdParty/boost/extension/impl/adaptable_factory_free_functions.hpp
    MMM/3rdParty/boost/extension/impl/create.hpp
    MMM/3rdParty/boost/extension/impl/factory.hpp
    MMM/3rdParty/boost/extension/impl/shared_library.hpp
    MMM/3rdParty/boost/extension/impl/create_func.hpp
    MMM/3rdParty/boost/extension/impl/adaptable_factory.hpp
    MMM/3rdParty/boost/extension/impl/decl.hpp
    MMM/3rdParty/boost/extension/impl/typeinfo.hpp
    MMM/3rdParty/boost/extension/impl/factory_map.hpp
    MMM/3rdParty/boost/extension/impl/function.hpp
    MMM/3rdParty/boost/extension/impl/library_impl.hpp
    MMM/3rdParty/boost/extension/impl/adaptable_factory_set.hpp
)

set(HEADER_FILES ${HEADER_FILES_base} ${HEADER_FILES_xml} ${HEADER_FILES_model} ${HEADER_FILES_motion} ${HEADER_FILES_legacymotion} ${HEADER_FILES_legacymotionconverter} ${HEADER_FILES_3rdParty_boost_extension} ${HEADER_FILES_3rdParty_boost_extension_impl})

###########################################################
#### CMake package configuration                       ####
###########################################################

find_package(Boost 1.48 REQUIRED filesystem unit_test_framework system)
set(EXTERNAL_INCLUDE_DIRS ${EXTERNAL_INCLUDE_DIRS} ${Boost_INCLUDE_DIRS})
set(EXTERNAL_LIBRARY_DIRS ${EXTERNAL_LIBRARY_DIRS} ${Boost_LIBRARIES})

# disable boost auto linking
# TODO: check if there is a real benefit from doing this
#set(EXTERNAL_LIBRARY_FLAGS ${EXTERNAL_LIBRARY_FLAGS} "-DBOOST_ALL_NO_LIB -DBOOST_PROGRAM_OPTIONS_DYN_LINK -DBOOST_FILESYSTEM_DYN_LINK -DBOOST_SYSTEM_DYN_LINK -DBOOST_UNIT_TEST_FRAMEWORK_DYN_LINK -DBOOST_THREAD_DYN_LINK")

find_package(Eigen3 REQUIRED)
set(EXTERNAL_INCLUDE_DIRS ${EXTERNAL_INCLUDE_DIRS} ${Eigen3_INCLUDE_DIR})

find_package(Alglib)
if(ALGLIB_FOUND)
    set(EXTERNAL_INCLUDE_DIRS ${EXTERNAL_INCLUDE_DIRS} ${ALGLIB_INCLUDES})
    set(EXTERNAL_LIBRARY_DIRS ${EXTERNAL_LIBRARY_DIRS} ${ALGLIB_LIBRARIES})
    set(EXTERNAL_LIBRARY_FLAGS ${EXTERNAL_LIBRARY_FLAGS} -DALGLIB_AVAILABLE)
endif(ALGLIB_FOUND)

find_package(Doxygen)

###########################################################
#### Doxygen configuration                             ####
###########################################################

if(DOXYGEN_FOUND)
    set(DOXYGEN_DOCUMENTATION_DIR "${PROJECT_BINARY_DIR}/doxygen")
    set(DOXYGEN_DOCUMENTATION_LAYOUT_DIR "${CMAKE_CURRENT_LIST_DIR}/etc/doxygen/layout")
    set(DOXYGEN_DOCUMENTATION_HTML_OUTPUT_DIR_REL "html")
    set(DOXYGEN_DOCUMENTATION_PAGES_DIR "${CMAKE_CURRENT_LIST_DIR}/etc/doxygen/pages")
    set(DOXYGEN_DOCUMENTATION_SNIPPET_DIR "${CMAKE_CURRENT_LIST_DIR}/etc/doxygen/snippets")
    set(DOXYGEN_DOCUMENTATION_IMAGE_DIR "${CMAKE_CURRENT_LIST_DIR}/etc/doxygen/images")
    configure_file(
        ${CMAKE_CURRENT_LIST_DIR}/etc/doxygen/layout/Doxyfile.in
        ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile
    )
    add_custom_target(doc
        ${DOXYGEN_EXECUTABLE} "${CMAKE_CURRENT_BINARY_DIR}/Doxyfile"
        WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}"
        COMMENT "Generating API documentation with Doxygen" VERBATIM
    )
endif(DOXYGEN_FOUND)

###########################################################
#### Compiler configuration                            ####
###########################################################

set(CMAKE_POSITION_INDEPENDENT_CODE ON) # enable -fPIC

if(MSVC)
    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /W4 /MP")
elseif(CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_GNUCXX)
    # cmake 3.10 does not understand c++2a, so we tell it we will handle the standard flag
    set(CMAKE_CXX_STANDARD_DEFAULT)
    add_definitions(-std=c++2a)
    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wno-long-long -pedantic")
endif()

add_compile_options(-Werror)

###########################################################
#### Project build configuration                       ####
###########################################################

#include_directories(${lib_INCS})
add_library(${CMAKE_PROJECT_NAME} SHARED ${SOURCE_FILES} ${HEADER_FILES})
target_link_libraries(${CMAKE_PROJECT_NAME} PUBLIC ${EXTERNAL_LIBRARY_DIRS} stdc++fs)

target_include_directories(${CMAKE_PROJECT_NAME} SYSTEM PUBLIC ${EXTERNAL_INCLUDE_DIRS})
target_compile_options(${CMAKE_PROJECT_NAME} PUBLIC ${EXTERNAL_LIBRARY_FLAGS})

set(SENSOR_PLUGIN_LIB_DIR "${CMAKE_BINARY_DIR}/sensorPluginLibs/")
target_compile_definitions(${CMAKE_PROJECT_NAME} PUBLIC -DSENSOR_PLUGIN_LIB_DIR="${SENSOR_PLUGIN_LIB_DIR}")


SET_TARGET_PROPERTIES(${CMAKE_PROJECT_NAME} PROPERTIES RUNTIME_OUTPUT_DIRECTORY bin)
SET_TARGET_PROPERTIES(${CMAKE_PROJECT_NAME} PROPERTIES LIBRARY_OUTPUT_DIRECTORY lib)
SET_TARGET_PROPERTIES(${CMAKE_PROJECT_NAME} PROPERTIES ARCHIVE_OUTPUT_DIRECTORY lib)

# Add sensor plugins
function(DefineMotionSensorPlugin SensorPlugin_Name SensorPlugin_Sources SensorPlugin_Headers)
    add_library(${SensorPlugin_Name} SHARED ${SensorPlugin_Sources} ${SensorPlugin_Headers})
    target_include_directories(${SensorPlugin_Name} SYSTEM PUBLIC ${EXTERNAL_INCLUDE_DIRS})
    target_link_libraries(${SensorPlugin_Name} PUBLIC ${CMAKE_PROJECT_NAME})
    set_target_properties(${SensorPlugin_Name} PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${SENSOR_PLUGIN_LIB_DIR})
    set(SensorPlugin_List ${SensorPlugin_List} ${SensorPlugin_Name} CACHE INTERNAL "" FORCE)
    get_filename_component(ProjectName ${CMAKE_CURRENT_SOURCE_DIR} NAME)
    install(FILES ${SensorPlugin_Headers} DESTINATION include/MMM/Motion/Plugin/${ProjectName} COMPONENT Devel)
    install(TARGETS ${SensorPlugin_Name} EXPORT "${CMAKE_PROJECT_NAME}Targets" LIBRARY DESTINATION lib/MMMSensorPlugins)
endfunction()

set(SensorPlugin_List "" CACHE INTERNAL "" FORCE)
add_subdirectory(MMM/Motion/Plugin)

# this allows dependant targets to automatically add include-dirs by simply linking against this project
target_include_directories(${CMAKE_PROJECT_NAME} PUBLIC
    $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
    $<INSTALL_INTERFACE:include>
)

# this allows to add 3rdParty libs to this project
target_link_libraries(${CMAKE_PROJECT_NAME} PUBLIC ${CMAKE_DL_LIBS})
target_include_directories(${CMAKE_PROJECT_NAME} PUBLIC
    $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/MMM/3rdParty>
    $<INSTALL_INTERFACE:include/MMM/3rdParty>
)

install(
    TARGETS "${CMAKE_PROJECT_NAME}"
    EXPORT "${CMAKE_PROJECT_NAME}Targets"
    LIBRARY DESTINATION lib
    ARCHIVE DESTINATION lib
    RUNTIME DESTINATION bin
    INCLUDES DESTINATION include
)

install (FILES ${HEADER_FILES_base} DESTINATION include/MMM COMPONENT Devel)
install (FILES ${HEADER_FILES_xml} DESTINATION include/MMM/RapidXML COMPONENT Devel)
install (FILES ${HEADER_FILES_model} DESTINATION include/MMM/Model COMPONENT Devel)
install (FILES ${HEADER_FILES_motion} DESTINATION include/MMM/Motion COMPONENT Devel)
install (FILES ${HEADER_FILES_legacymotion} DESTINATION include/MMM/Motion/Legacy COMPONENT Devel)
install (FILES ${HEADER_FILES_legacymotionconverter} DESTINATION include/MMM/Motion/Legacy/Converter COMPONENT Devel)
install (FILES ${HEADER_FILES_3rdParty_boost_extension} DESTINATION include/MMM/3rdParty/boost/extension COMPONENT Devel)
install (FILES ${HEADER_FILES_3rdParty_boost_extension_impl} DESTINATION include/MMM/3rdParty/boost/extension/impl COMPONENT Devel)

export(
    TARGETS ${CMAKE_PROJECT_NAME} ${SensorPlugin_List}
    FILE "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_PROJECT_NAME}/${CMAKE_PROJECT_NAME}Targets.cmake"
)

configure_file(
    "CMakeModules/${CMAKE_PROJECT_NAME}Config.cmake"
    "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_PROJECT_NAME}/${CMAKE_PROJECT_NAME}Config.cmake"
    COPYONLY
)

set(INSTALL_CMAKE_DIR "share/cmake/${CMAKE_PROJECT_NAME}")
install(
    EXPORT "${CMAKE_PROJECT_NAME}Targets"
    FILE "${CMAKE_PROJECT_NAME}Targets.cmake"
    DESTINATION ${INSTALL_CMAKE_DIR}
)
install(
    FILES
        "CMakeModules/${CMAKE_PROJECT_NAME}Config.cmake"
        "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_PROJECT_NAME}/${CMAKE_PROJECT_NAME}ConfigVersion.cmake"
    DESTINATION ${INSTALL_CMAKE_DIR}
    COMPONENT Devel
)

export(PACKAGE ${CMAKE_PROJECT_NAME})

###########################################################
#### Version configuration                             ####
###########################################################

set_property(TARGET ${CMAKE_PROJECT_NAME} PROPERTY VERSION ${PROJ_VERSION})
set_property(TARGET ${CMAKE_PROJECT_NAME} PROPERTY SOVERSION ${PROJ_SO_VERSION})

include(CMakePackageConfigHelpers)
write_basic_package_version_file(
    "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_PROJECT_NAME}/${CMAKE_PROJECT_NAME}ConfigVersion.cmake"
    VERSION ${PROJ_VERSION}
    COMPATIBILITY AnyNewerVersion
)

###########################################################
#### Debian package                                    ####
###########################################################

find_package(Git QUIET)
if(GIT_FOUND)
    # output format <tag>-<commit-id>-dirty or <tag>-<commit-id> or <commit-id>
    execute_process(COMMAND ${GIT_EXECUTABLE} describe --long --tags --dirty --always
        WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} RESULT_VARIABLE  GIT_EXIT_STATUS
        OUTPUT_VARIABLE MMMCore_PACKAGE_GIT_VERSION OUTPUT_STRIP_TRAILING_WHITESPACE)
    if(NOT GIT_EXIT_STATUS EQUAL 0)
        message(WARNING "no git repository or unable to run git describe")
    else()
        message(STATUS "git tag: ${MMMCore_PACKAGE_GIT_VERSION}")
        # test if git describe finds a tag.  the commit id is separated by a dash.
        string(REGEX MATCH "^(v)?[0-9]+\\.[0-9]+\\.[0-9]+-0-[a-z0-9]+$" GIT_TAG_FOUND ${MMMCore_PACKAGE_GIT_VERSION})

        if(GIT_TAG_FOUND)
            string(REPLACE "v" "" MMMCore_PACKAGE_GIT_VERSION ${MMMCore_PACKAGE_GIT_VERSION})
            if(NOT ${MMMCore_PACKAGE_GIT_VERSION} MATCHES "^${PROJ_VERSION}.*")
                message(WARNING "unable to determine current version. package version is ${PROJ_VERSION}. git tag is ${MMMCore_PACKAGE_GIT_VERSION}.")
            else()
                set(CPACK_GENERATOR "DEB")
                set(CPACK_PACKAGE_CONTACT "Tamim Asfour <tamim.asfour@kit.edu>")
                set(CPACK_PACKAGE_VENDOR "KIT")
                set(CPACK_PACKAGE_VERSION "${PROJ_VERSION}")
            endif()
        else()
            message(STATUS "current git tag ${MMMCORE_PACKAGE_GIT_VERSION} is not a version")
        endif()
    endif()
else()
    message(WARNING "git command not found. unable to determine current git tag.")
endif()

include(CPack)

###########################################################
### Setup for testing                                   ###
###########################################################

function(DefineTest Test_Name Test_Src Test_Libs Sensors)
    add_executable(${Test_Name} ${Test_Src} ${Test_Libs})
    target_link_libraries(${Test_Name}
                          ${CMAKE_PROJECT_NAME}
                          ${Sensors})
    target_compile_definitions(${Test_Name} PUBLIC -DBOOST_TEST_DYN_LINK)
    set_target_properties(${Test_Name} PROPERTIES
        RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/tests_bin)
    add_test(NAME ${Test_Name}
        WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/tests_bin
        COMMAND ${CMAKE_CURRENT_BINARY_DIR}/tests_bin/${Test_Name})
endfunction()

ENABLE_TESTING()
add_subdirectory(MMM/tests)
